package com.gitee.yongzhuzl.commonutil.util.file;

import com.gitee.yongzhuzl.commonutil.util.ftp.FtpUtil;
import com.gitee.yongzhuzl.commonutil.util.json.JsonUtil;
import com.gitee.yongzhuzl.commonutil.util.string.StringUtil;
import com.gitee.yongzhuzl.commonutil.util.empty.EmptyUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * description: 文件上传工具类
 * @author 朱正磊
 * @date 2018年7月27日 下午8:37:31
 */
public class FileUploadUtil {

	private static final Logger log = LoggerFactory.getLogger(FileUploadUtil.class);

	/**
	 * description: 上传文件（比如图片、PDF文件等等）
	 * @param file 文件对象
	 * @param filePath 文件目录，比如：D:\img\，其中最后一个\不能省略
	 * @return 返回文件全路径
	 * @throws Exception 异常信息
	 */
	public static String uploadFile(MultipartFile file, String filePath) throws Exception {
		String originalFilename = file.getOriginalFilename();
		String fileFullPath = "";
        assert originalFilename != null;
        if (!originalFilename.isEmpty()) {
			// 保证确实有上传文件存在
			if(createDir(filePath)) {
				// 文件夹存在或者创建成功
				String newFilename = UUID.randomUUID() + originalFilename
						.substring(originalFilename.lastIndexOf("."));
				fileFullPath = filePath + newFilename;
                //noinspection JvmTaintAnalysis
                File newFile = new File(fileFullPath);
				file.transferTo(newFile);
			}
		}
		return fileFullPath;
	}

    /**
     * description: 上传文件到指定目录
     * @author 朱正磊
     * @date 2025-08-29 14:47:37
     * @param file 待上传的文件
     * @param baseDir 文件存储的基础目录（绝对路径），比如：D:\img 或者 D:\img\ 都可以
     * @return 上传后的文件完整路径
     * @throws IOException 当文件处理失败时抛出
     */
    public static String upFile(MultipartFile file, String baseDir) throws IOException {
        // 1. 基础校验：避免空文件和无效参数
        if (file == null || file.isEmpty()) {
            log.error("上传文件为空");
            throw new IllegalArgumentException("上传文件不能为空");
        }

        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null || originalFilename.trim().isEmpty()) {
            log.error("文件名无效");
            throw new IllegalArgumentException("文件名不能为空");
        }

        // 2. 处理文件名：生成唯一文件名，安全提取扩展名
        String fileExtension = getFileExtension(originalFilename);
        String newFilename = UUID.randomUUID() + (fileExtension.isEmpty() ? "" : "." + fileExtension);

        // 3. 安全处理文件路径：使用NIO API避免路径遍历和跨平台问题
        Path basePath = Paths.get(baseDir).toAbsolutePath().normalize();
        Path targetPath = basePath.resolve(newFilename).normalize();

        // 验证目标路径是否在基础目录内（防止路径遍历攻击）
        if (!targetPath.startsWith(basePath)) {
            log.error("非法的文件路径：{}（尝试突破基础目录{}）", targetPath, basePath);
            throw new SecurityException("文件存储路径不合法");
        }

        // 4. 确保目录存在（自动创建多级目录）
        Files.createDirectories(basePath);

        // 5. 传输文件（使用NIO的transferTo，更高效）
        file.transferTo(targetPath);
        log.info("文件上传成功：{} -> {}", originalFilename, targetPath);

        return targetPath.toString();
    }

    /**
     * description: 安全提取文件扩展名（不含点）
     * @author 朱正磊
     * @date 2025-08-29 14:47:45
     * @param filename 原始文件名
     * @return 扩展名（如"txt"），无扩展名则返回空字符串
     */
    private static String getFileExtension(String filename) {
        int lastDotIndex = filename.lastIndexOf('.');
        // 处理无扩展名或文件名以点开头的情况（如".gitignore"）
        if (lastDotIndex <= 0 || lastDotIndex == filename.length() - 1) {
            return "";
        }
        return filename.substring(lastDotIndex + 1).toLowerCase();
    }

	/**
	 * description: 读取绝对路径下的文件（比如图片、PDF文件等等）
	 * @param response 响应对象
	 * @param fileFullPath 文件全路径
	 */
	public static void readFile(HttpServletResponse response, String fileFullPath) {
        log.info("文件地址：{}", fileFullPath);
		read(response, fileFullPath);
	}

	/**
	 * 说明：如果文件不存在则返回false，如果文件的类型不是文件类型（有可能是文件夹即文件目录）则返回false，
	 * 		如果文件删除不成功则返回false，否则返回true。
	 * description: 删除文件
	 * @param fileFullPath 文件全路径
	 * @return 返回删除文件（不是文件夹）状态
	 */
	public static boolean deleteFile(String fileFullPath) {
		File file = new File(fileFullPath);
		return file.exists() && file.isFile() && file.delete();
	}
	
	/**
	 * 说明：该方法是临时方法，使用场景是：使用tomcat服务器而不是maven的tomcat插件，但是没有项目名的情况
	 * description: 获取tomcat服务器webapps所在的绝对路径
	 * @return 返回tomcat服务器webapps所在的绝对路径
	 */
	public static String getServerPathTemp() {
		// 根据项目名写死
		String projectName = "/projectName";
		String classPath = Objects.requireNonNull(FileUploadUtil.class.getClassLoader().getResource("")).getPath();
		String serverPath = classPath.replace(projectName + "/WEB-INF/classes/", "");
		// 有利于debug观察
        log.info("获取tomcat服务器webapps所在的绝对路径：{}", serverPath);
		return serverPath;
	}
	
	/**
	 * 提醒：不支持maven的tomcat插件启动方式
	 * description: 获取tomcat服务器webapps所在的绝对路径（有缺陷，考虑不周全）
	 * @param request 请求对象
	 * @return 返回tomcat服务器webapps所在的绝对路径
	 */
	public static String getServerPath_(HttpServletRequest request) {
		String projectName = request.getContextPath();
		if (EmptyUtil.isEmpty(projectName)) {
			projectName = "/ROOT";
		}
		log.info("项目名：{}", projectName);
		
		String classPath = Objects.requireNonNull(FileUploadUtil.class.getClassLoader().getResource("")).getPath();
		log.info("类路径：{}", classPath);
		
		// 方式一
		String serverPath_1 = classPath.replace(projectName + "/WEB-INF/classes/", "");
		log.info("tomcat服务器webapps所在的绝对路径 = {}", serverPath_1);
		
		// 方式二
		String serverPath_2 = classPath.substring(0, classPath.indexOf(projectName));
		log.info("tomcat服务器webapps所在的绝对路径: {}", serverPath_2);
		
		return serverPath_2;
	}
	
	/**
	 * 提醒：不支持maven的tomcat插件启动方式
	 * description: 获取tomcat服务器webapps所在的绝对路径（无缺陷，考虑很周全）
	 * @return 返回tomcat服务器webapps所在的绝对路径
	 */
	public static String getServerPath() {
		String classPath = Objects.requireNonNull(FileUploadUtil.class.getClassLoader().getResource("")).getPath();
		log.info("类路径: {}", classPath);
		
		Integer lastIndex = StringUtil.getLastIndex(classPath, "/", 4);
		
		String serverPath = classPath.substring(0, lastIndex);
		log.info("tomcat服务器webapps所在的绝对路径：{}", serverPath);
		
		return serverPath;
	}

	/**
	 * description: 上传图片并获取图片访问url地址（上传到tomcat服务器）
	 * @param request 请求对象
	 * @param fileName 页面请求file标签name属性值
	 * @param imageUploadPath 图片上传路径，文件分隔符不能省略，比如：\img\，其中最前面的\和最后面的\都不能省略
	 * @param imageServerUrlPrefix 图片访问url地址前缀
	 * @return 如果没有上传图片那么返回空字符串，否则返回图片访问url地址
	 * @throws Exception 异常
	 */
	public static String getImageUrl(HttpServletRequest request, 
			String fileName, String imageUploadPath, String imageServerUrlPrefix) throws Exception {
		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
		MultipartFile multipartFile = multipartRequest.getFile(fileName);
		String imageUrl = "";
        assert multipartFile != null;
        if (multipartFile.getSize() > 0) {
			// 如果没有上传图片或者说没有修改图片，那么文件的大小就是0，否则确实有上传图片的话文件的大小就必然大于0
			String imagePath = getServerPath() + imageUploadPath;
			log.info("上传图片绝对路径（文件所在目录）：{}", imagePath);
			
			String fileFullPath = uploadFile(multipartFile, imagePath);
			log.info("上传图片绝对全路径包括图片名以及后缀：{}", fileFullPath);
			
			if (EmptyUtil.isNotEmpty(fileFullPath)) {
				// 保证确实有上传文件存在
				String uploadImage = fileFullPath.substring(fileFullPath.indexOf(imageUploadPath));
				log.info("上传图片相对全路径包括图片名以及后缀：{}", uploadImage);
				
				imageUrl = imageServerUrlPrefix + uploadImage;
				log.info("图片访问url地址：{}", imageUrl);
			}
		}
		return imageUrl;
	}
	
	/**
	 * 提醒：该方法同样适用于视频文件的上传！
	 * description: 上传图片到nginx图片服务器并获取上传图片的服务访问url地址
	 * @param request 请求对象
	 * @param projectName 项目名称
	 * @param modelName 模块名称
	 * @param pictureName 表单file标签的name属性值
	 * @param paramValue ftp服务信息
	 * @return 返回图片url相关信息
	 */
	public static String getPictureUrl(HttpServletRequest request, String projectName, String modelName, String pictureName, String paramValue) {
		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
		List<MultipartFile>  multipartFiles = multipartRequest.getFiles(pictureName);
		if (EmptyUtil.isEmpty(multipartFiles)) {
			// 如果集合元素个数为0个，即没有进行图片上传（或者修改页面图片没有进行修改）
			return null;
		}
		String originalFilename = multipartFiles.get(0).getOriginalFilename();
		String pictureUrl = null;
		if (EmptyUtil.isNotEmpty(originalFilename)) {
			// 如果有上传图片
            Map<String, Object> map = JsonUtil.toMap(paramValue, String.class, Object.class);
			// 比如：/adminNew/developerSettleAct，根据日期添加子目录比如/2018-08-23
			String filePath = "/" + projectName + "/" + modelName;
			// 比如：http://192.168.0.204:10000
			String httpPath = (String) map.get("httpPath");
			Map<String, Object> resultMap = FtpUtil
                    .uploadPicture(multipartFiles, (String) map.get("host"), (Integer) map.get("port"),
                            (String) map.get("userName"), (String) map.get("password"),
                            (String) map.get("basePath"), filePath, httpPath);
			if (Objects.equals(1, resultMap.get("error"))) {
				log.info("【getPictureUrl上传图片出错：{}】", resultMap.get("message"));
			} else {
				pictureUrl = resultMap.get("url").toString();
			}
		}
		return pictureUrl;
	}
	
	/**
	 * description: 获取上传文件对象集合
	 * @param request 请求对象
	 * @param fileName 表单file标签的name属性值
	 * @return 返回上传文件对象集合
	 */
	public static List<MultipartFile> getMultipartFiles(HttpServletRequest request, String fileName) {
		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
		List<MultipartFile> multipartFiles = multipartRequest.getFiles(fileName);
		if (EmptyUtil.isNotEmpty(multipartFiles)) {
			MultipartFile multipartFile = multipartFiles.get(0);
			if (EmptyUtil.isNotEmpty(multipartFile)) {
				// 获取上传的文件名
				String originalFilename = multipartFile.getOriginalFilename();
				// 文件名为""即空字符串，则表示没有上传文件
				if (EmptyUtil.isEmpty(originalFilename)) {
					return null;
				}
			} else {
				return null;
			}
		} else {
			return null;
		}
		return multipartFiles;
	}

	/**
	 * description: 获取表单中所有的上传文件标签的name的值对应的文件集合
	 * @param request 请求对象
	 * @return 返回map集合，key指的是上传文件标签的name的值，value指的是文件集合
	 */
	public static Map<String, List<MultipartFile>> getMultipartFiles(HttpServletRequest request) {
		Map<String, List<MultipartFile>> map = new HashMap<>();
		MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
		// 获取提交表单中所有的file标签的name属性的值
		Iterator<String> names = multipartRequest.getFileNames();
		while (names.hasNext()) {
			String name = names.next();
			List<MultipartFile> multipartFiles = getMultipartFiles(request, name);
			map.put(name, multipartFiles);
		}
		return map;
	}

	/**
	 * description: 借助FTP服务器将文件删除（从NGINX服务器里面删除）
	 * @param nginxParams NGINX服务器配置参数
	 * @param fileUrl 文件访问url地址
	 * @param projectName 项目名称
	 * @param modelName 模块名称
	 * @return 返回true表示删除文件成功，返回false表示删除文件失败
	 */
	public static boolean deleteFileFromNginxByFtp(String nginxParams, String fileUrl, String projectName, String modelName) {
        Map<String, Object> map = JsonUtil.toMap(nginxParams, String.class, Object.class);
		Integer lastIndexTwo = StringUtil.getLastIndex(fileUrl, "/", 2);
		Integer lastIndexOne = StringUtil.getLastIndex(fileUrl, "/", 1);
		// 比如：/2018-08-23
		String dateName = fileUrl.substring(lastIndexTwo, lastIndexOne);
		// 比如：/education/videoInfo/2018-08-23
		String filePath = "/" + projectName + "/" + modelName + dateName;
		// 比如：gaigeming.jpg
		String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
		return FtpUtil.deleteFileFtp((String) map.get("host"), (Integer) map.get("port"), (String) map.get("userName"),
                (String) map.get("password"), (String) map.get("basePath"), filePath, fileName);
	}
	
	/**
	 * 注意：mkdirs是创建多级文件夹（建议使用），mkdir是创建单级文件夹（不建议使用），createNewFile是创建空文件。
	 * 说明：如果文件目录存在则返回true，如果文件目录不存在就创建多级目录，
	 * 		如果多级目录创建成功则返回true，创建失败返回false。
	 * description: 如果文件目录不存在就创建，有就不再创建
	 * @param fileDir 文件目录
	 * @return 返回创建文件目录状态
	 */
	private static boolean createDir(String fileDir) {
		File dir = new File(fileDir);
		return dir.exists() || dir.mkdirs();
	}

	/**
	 * description: 读取文件
	 * @param response 响应对象
	 * @param fileFullPath 文件全路径
	 */
	private static void read(HttpServletResponse response, String fileFullPath) {
		FileInputStream input = null;
		OutputStream output = null;
		if (StringUtils.isNotBlank(fileFullPath)) {
			// 文件全路径不能为null或者""
			File file = new File(fileFullPath);
			if (file.exists()) {
				// 文件首先要确保存在
				try {
					input = new FileInputStream(fileFullPath);
					output = response.getOutputStream();
					int count;
					byte[] buffer = new byte[1024 * 8];
					while ((count = input.read(buffer)) != -1) {
						output.write(buffer, 0, count);
						output.flush();
					}
				} catch (IOException e) {
					log.error("读取失败：", e);
				} finally {
					try {
						if (output != null) {
							output.close();
						}
						if (input != null) {
							input.close();
						}
					} catch (Exception e) {
						log.error("文件错误：", e);
					}
				}
			}
		}
	}

}
